From 44bdb81a60d3a44ba7e379f3c20fe6d8fb284339 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 7 Jul 2025 08:24:16 +0000 Subject: (대표님) 변경사항 20250707 12시 30분 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[lng]/admin/mdg/page.tsx | 277 --------------------------- app/[lng]/admin/mdg/page.tsx.bak | 277 +++++++++++++++++++++++++++ app/[lng]/admin/mdg/todo.md | 2 + app/[lng]/evcp/(evcp)/login-history/page.tsx | 2 - app/[lng]/evcp/(evcp)/page-visits/page.tsx | 61 ++++++ app/[lng]/evcp/(evcp)/report/page.tsx | 69 +++---- 6 files changed, 362 insertions(+), 326 deletions(-) delete mode 100644 app/[lng]/admin/mdg/page.tsx create mode 100644 app/[lng]/admin/mdg/page.tsx.bak create mode 100644 app/[lng]/admin/mdg/todo.md create mode 100644 app/[lng]/evcp/(evcp)/page-visits/page.tsx (limited to 'app/[lng]') diff --git a/app/[lng]/admin/mdg/page.tsx b/app/[lng]/admin/mdg/page.tsx deleted file mode 100644 index e2926deb..00000000 --- a/app/[lng]/admin/mdg/page.tsx +++ /dev/null @@ -1,277 +0,0 @@ -'use client' - -import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Badge } from '@/components/ui/badge' -import { toast } from 'sonner' -import { Loader2, Send, RefreshCw } from 'lucide-react' - -// CSV 필드를 정의할 타입 -interface VendorFieldDef { - table: string; - name: string; - mandatory: boolean; - description: string; -} - -// CSV 파싱 함수 (간단 파서) -const parseCSV = (csv: string): VendorFieldDef[] => { - const lines = csv.trim().split('\n'); - // 첫 번째 라인은 헤더이므로 제거 - return lines.slice(1).map((line) => { - const parts = line.split(','); - const table = parts[1]?.trim(); - const name = parts[2]?.trim(); - const mandatory = parts[3]?.trim() === 'M'; - const description = parts.slice(6).join(',').trim(); - return { table, name, mandatory, description } as VendorFieldDef; - }); -}; - -// 기존 샘플 기본값 (필요 시 확장) -const sampleDefaults: Record = { - BP_HEADER: 'TEST001', - ZZSRMCD: 'EVCP', - TITLE: 'TEST', - BU_SORT1: 'TEST VENDOR', - NAME_ORG1: '테스트 벤더 회사', - KTOKK: 'Z001', - VEN_KFBUS: '제조업', - VEN_KFIND: 'IT', - MASTERFLAG: 'X', - IBND_TYPE: 'U', - ZZREQID: 'TESTUSER01', - ADDRNO: '0001', - AD_NATION: '1', - COUNTRY: 'KR', - LANGU_COM: 'K', - POST_COD1: '06292', - CITY1: '서울시', - DISTRICT: '강남구', - REGION: '11', - MC_STREET: '테헤란로 123', - T_COUNTRY: 'KR', - T_NUMBER: '02-1234-5678', - F_COUNTRY: 'KR', - F_NUMBER: '02-1234-5679', - U_ADDRESS: 'https://test.vendor.com', - E_ADDRESS: 'contact@test.vendor.com', - BP_TX_TYP: 'KR2', - TAXNUM: '123-45-67890', - AD_CONSNO: '1', -}; - -// XML escape helper -const escapeXml = (unsafe: string) => unsafe.replace(/[<>&'"']/g, (c) => { - switch (c) { - case '<': return '<'; - case '>': return '>'; - case '&': return '&'; - case '"': return '"'; - case "'": return '''; - default: return c; - } -}); - -export default function MDGTestPage() { - const [formData, setFormData] = useState>({}); - const [fieldDefs, setFieldDefs] = useState([]); - const [resultXml, setResultXml] = useState(''); - const [isLoading, setIsLoading] = useState(false); - - // CSV 로딩 및 초기 데이터 셋업 - useEffect(() => { - const load = async () => { - const res = await fetch('/wsdl/P2MD3007_AO.csv'); - const csvText = await res.text(); - const defs = parseCSV(csvText); - setFieldDefs(defs); - - const init: Record = {}; - defs.forEach((d) => { - init[d.name] = sampleDefaults[d.name] ?? ''; - }); - setFormData(init); - }; - - load(); - }, []); - - // XML 생성 유틸리티 (폼 데이터 -> SOAP Envelope) - const buildEnvelopeXml = (currentForm: Record, defs: VendorFieldDef[]) => { - if (defs.length === 0) return ''; - const bodyContent = defs.map((f) => { - const val = currentForm[f.name] ?? ''; - return `<${f.name}>${escapeXml(val)}`; - }).join('\n '); - - const supplierXml = `\n ${bodyContent}\n `; - - return `\n\n \n \n \n \n ${supplierXml}\n \n \n \n`; - }; - - // 폼 데이터 변경 시 실시간 XML 생성 - useEffect(() => { - const xml = buildEnvelopeXml(formData, fieldDefs); - setResultXml(xml); - }, [formData, fieldDefs]); - - // 폼 데이터 업데이트 - const updateField = (field: string, value: string) => { - setFormData(prev => ({ ...prev, [field]: value })); - }; - - // 기본값으로 리셋 - const resetForm = () => { - const reset: Record = {}; - fieldDefs.forEach((d) => { - reset[d.name] = sampleDefaults[d.name] ?? ''; - }); - setFormData(reset); - toast.success('폼이 기본값으로 리셋되었습니다.'); - }; - - // 테스트 송신 실행 (실제 서버 호출) - const handleTestSend = async () => { - try { - setIsLoading(true); - - // 필수 필드 검증 - const requiredFields = fieldDefs.filter(d => d.mandatory).map(d => d.name); - const missingFields = requiredFields.filter(field => !formData[field]?.trim()); - - if (missingFields.length > 0) { - toast.error(`필수 필드가 누락되었습니다: ${missingFields.join(', ')}`); - setIsLoading(false); - return; - } - - // 서버 API 호출해 송신 - const res = await fetch('/api/mdg/send-vendor-xml', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ envelope: resultXml }), - }); - - const json = await res.json(); - - if (!res.ok || !json.success) { - // 상세 오류 메시지 추출 (vendorCode 기반 또는 직접 오류 메시지) - const detailMsg = json?.results?.[0]?.error ?? json?.message ?? json?.responseText ?? '송신 실패'; - toast.error(`송신 실패: ${detailMsg}`); - setIsLoading(false); - return; - } - - toast.success('MDG 송신이 완료되었습니다.'); - - } catch (error) { - console.error('테스트 송신 실패:', error); - toast.error('테스트 송신 중 오류가 발생했습니다.'); - } finally { - setIsLoading(false); - } - }; - - return ( -
-
-
-

MDG VENDOR 마스터 테스트

-

- VENDOR 마스터 데이터를 MDG 시스템으로 테스트 송신합니다 -

-
-
- - -
-
- - {/* 동적 필드 렌더링 */} - {fieldDefs.length === 0 ? ( -

CSV 로딩 중...

- ) : ( -
- {Object.entries( - fieldDefs.reduce((acc: Record, cur) => { - acc[cur.table] = acc[cur.table] ? [...acc[cur.table], cur] : [cur]; - return acc; - }, {}) - ).map(([table, fields]) => ( - - - - {table} - {fields.some(f => f.mandatory) && ( - 필수 포함 - )} - - {table} 테이블 입력 - - - {fields.filter((f, idx, arr) => arr.findIndex(x => x.name === f.name) === idx).map((field) => ( -
- - updateField(field.name, e.target.value)} - /> - {field.description && ( -

{field.description}

- )} -
- ))} -
-
- ))} -
- )} - - {/* 송신 결과 영역 */} - - - 송신 결과 - - MDG 시스템으로의 송신 결과가 여기에 표시됩니다 - - - - {resultXml ? ( -
-              {resultXml}
-            
- ) : ( -
-

- 테스트 송신 버튼을 클릭하면 결과가 표시됩니다. -

-
- )} -
-
-
- ); -} - diff --git a/app/[lng]/admin/mdg/page.tsx.bak b/app/[lng]/admin/mdg/page.tsx.bak new file mode 100644 index 00000000..e2926deb --- /dev/null +++ b/app/[lng]/admin/mdg/page.tsx.bak @@ -0,0 +1,277 @@ +'use client' + +import { useState, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Badge } from '@/components/ui/badge' +import { toast } from 'sonner' +import { Loader2, Send, RefreshCw } from 'lucide-react' + +// CSV 필드를 정의할 타입 +interface VendorFieldDef { + table: string; + name: string; + mandatory: boolean; + description: string; +} + +// CSV 파싱 함수 (간단 파서) +const parseCSV = (csv: string): VendorFieldDef[] => { + const lines = csv.trim().split('\n'); + // 첫 번째 라인은 헤더이므로 제거 + return lines.slice(1).map((line) => { + const parts = line.split(','); + const table = parts[1]?.trim(); + const name = parts[2]?.trim(); + const mandatory = parts[3]?.trim() === 'M'; + const description = parts.slice(6).join(',').trim(); + return { table, name, mandatory, description } as VendorFieldDef; + }); +}; + +// 기존 샘플 기본값 (필요 시 확장) +const sampleDefaults: Record = { + BP_HEADER: 'TEST001', + ZZSRMCD: 'EVCP', + TITLE: 'TEST', + BU_SORT1: 'TEST VENDOR', + NAME_ORG1: '테스트 벤더 회사', + KTOKK: 'Z001', + VEN_KFBUS: '제조업', + VEN_KFIND: 'IT', + MASTERFLAG: 'X', + IBND_TYPE: 'U', + ZZREQID: 'TESTUSER01', + ADDRNO: '0001', + AD_NATION: '1', + COUNTRY: 'KR', + LANGU_COM: 'K', + POST_COD1: '06292', + CITY1: '서울시', + DISTRICT: '강남구', + REGION: '11', + MC_STREET: '테헤란로 123', + T_COUNTRY: 'KR', + T_NUMBER: '02-1234-5678', + F_COUNTRY: 'KR', + F_NUMBER: '02-1234-5679', + U_ADDRESS: 'https://test.vendor.com', + E_ADDRESS: 'contact@test.vendor.com', + BP_TX_TYP: 'KR2', + TAXNUM: '123-45-67890', + AD_CONSNO: '1', +}; + +// XML escape helper +const escapeXml = (unsafe: string) => unsafe.replace(/[<>&'"']/g, (c) => { + switch (c) { + case '<': return '<'; + case '>': return '>'; + case '&': return '&'; + case '"': return '"'; + case "'": return '''; + default: return c; + } +}); + +export default function MDGTestPage() { + const [formData, setFormData] = useState>({}); + const [fieldDefs, setFieldDefs] = useState([]); + const [resultXml, setResultXml] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + // CSV 로딩 및 초기 데이터 셋업 + useEffect(() => { + const load = async () => { + const res = await fetch('/wsdl/P2MD3007_AO.csv'); + const csvText = await res.text(); + const defs = parseCSV(csvText); + setFieldDefs(defs); + + const init: Record = {}; + defs.forEach((d) => { + init[d.name] = sampleDefaults[d.name] ?? ''; + }); + setFormData(init); + }; + + load(); + }, []); + + // XML 생성 유틸리티 (폼 데이터 -> SOAP Envelope) + const buildEnvelopeXml = (currentForm: Record, defs: VendorFieldDef[]) => { + if (defs.length === 0) return ''; + const bodyContent = defs.map((f) => { + const val = currentForm[f.name] ?? ''; + return `<${f.name}>${escapeXml(val)}`; + }).join('\n '); + + const supplierXml = `\n ${bodyContent}\n `; + + return `\n\n \n \n \n \n ${supplierXml}\n \n \n \n`; + }; + + // 폼 데이터 변경 시 실시간 XML 생성 + useEffect(() => { + const xml = buildEnvelopeXml(formData, fieldDefs); + setResultXml(xml); + }, [formData, fieldDefs]); + + // 폼 데이터 업데이트 + const updateField = (field: string, value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + // 기본값으로 리셋 + const resetForm = () => { + const reset: Record = {}; + fieldDefs.forEach((d) => { + reset[d.name] = sampleDefaults[d.name] ?? ''; + }); + setFormData(reset); + toast.success('폼이 기본값으로 리셋되었습니다.'); + }; + + // 테스트 송신 실행 (실제 서버 호출) + const handleTestSend = async () => { + try { + setIsLoading(true); + + // 필수 필드 검증 + const requiredFields = fieldDefs.filter(d => d.mandatory).map(d => d.name); + const missingFields = requiredFields.filter(field => !formData[field]?.trim()); + + if (missingFields.length > 0) { + toast.error(`필수 필드가 누락되었습니다: ${missingFields.join(', ')}`); + setIsLoading(false); + return; + } + + // 서버 API 호출해 송신 + const res = await fetch('/api/mdg/send-vendor-xml', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ envelope: resultXml }), + }); + + const json = await res.json(); + + if (!res.ok || !json.success) { + // 상세 오류 메시지 추출 (vendorCode 기반 또는 직접 오류 메시지) + const detailMsg = json?.results?.[0]?.error ?? json?.message ?? json?.responseText ?? '송신 실패'; + toast.error(`송신 실패: ${detailMsg}`); + setIsLoading(false); + return; + } + + toast.success('MDG 송신이 완료되었습니다.'); + + } catch (error) { + console.error('테스트 송신 실패:', error); + toast.error('테스트 송신 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+

MDG VENDOR 마스터 테스트

+

+ VENDOR 마스터 데이터를 MDG 시스템으로 테스트 송신합니다 +

+
+
+ + +
+
+ + {/* 동적 필드 렌더링 */} + {fieldDefs.length === 0 ? ( +

CSV 로딩 중...

+ ) : ( +
+ {Object.entries( + fieldDefs.reduce((acc: Record, cur) => { + acc[cur.table] = acc[cur.table] ? [...acc[cur.table], cur] : [cur]; + return acc; + }, {}) + ).map(([table, fields]) => ( + + + + {table} + {fields.some(f => f.mandatory) && ( + 필수 포함 + )} + + {table} 테이블 입력 + + + {fields.filter((f, idx, arr) => arr.findIndex(x => x.name === f.name) === idx).map((field) => ( +
+ + updateField(field.name, e.target.value)} + /> + {field.description && ( +

{field.description}

+ )} +
+ ))} +
+
+ ))} +
+ )} + + {/* 송신 결과 영역 */} + + + 송신 결과 + + MDG 시스템으로의 송신 결과가 여기에 표시됩니다 + + + + {resultXml ? ( +
+              {resultXml}
+            
+ ) : ( +
+

+ 테스트 송신 버튼을 클릭하면 결과가 표시됩니다. +

+
+ )} +
+
+
+ ); +} + diff --git a/app/[lng]/admin/mdg/todo.md b/app/[lng]/admin/mdg/todo.md new file mode 100644 index 00000000..497199a9 --- /dev/null +++ b/app/[lng]/admin/mdg/todo.md @@ -0,0 +1,2 @@ +라우터에서 [project] 세그먼트를 요구하는 문제가 있음 +이 페이지 수정 필요 ? \ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/login-history/page.tsx b/app/[lng]/evcp/(evcp)/login-history/page.tsx index af9c94f2..bd4d42cb 100644 --- a/app/[lng]/evcp/(evcp)/login-history/page.tsx +++ b/app/[lng]/evcp/(evcp)/login-history/page.tsx @@ -6,7 +6,6 @@ import { Skeleton } from "@/components/ui/skeleton" import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" import { Shell } from "@/components/shell" -import { InformationButton } from "@/components/information/information-button" import { getLoginSessions } from "@/lib/login-session/service" import { searchParamsCache } from "@/lib/login-session/validation" import { LoginSessionsTable } from "@/lib/login-session/table/login-sessions-table" @@ -37,7 +36,6 @@ export default async function LoginHistoryPage(props: LoginHistoryPageProps) {

로그인 세션 이력

-

사용자의 로그인/로그아웃 이력과 세션 정보를 확인할 수 있습니다. diff --git a/app/[lng]/evcp/(evcp)/page-visits/page.tsx b/app/[lng]/evcp/(evcp)/page-visits/page.tsx new file mode 100644 index 00000000..38386d51 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/page-visits/page.tsx @@ -0,0 +1,61 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { getPageVisits } from "@/lib/page-visits/service" +import { searchParamsCache } from "@/lib/page-visits/validation" +import { PageVisitsTable } from "@/lib/page-visits/table/page-visits-table" + +interface PageVisitsPageProps { + searchParams: Promise +} + +export default async function PageVisitsPage(props: PageVisitsPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getPageVisits({ + ...search, + filters: validFilters, + }), + ]) + + return ( + +

+
+
+
+

+ 페이지 방문 이력 +

+
+

+ 사용자의 페이지별 방문 이력과 활동 패턴을 확인할 수 있습니다. +

+
+
+
+ + + } + > + + + + ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/report/page.tsx b/app/[lng]/evcp/(evcp)/report/page.tsx index f84ebe52..a538b37c 100644 --- a/app/[lng]/evcp/(evcp)/report/page.tsx +++ b/app/[lng]/evcp/(evcp)/report/page.tsx @@ -1,31 +1,38 @@ -// app/procurement/dashboard/page.tsx import * as React from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { Shell } from "@/components/shell"; import { ErrorBoundary } from "@/components/error-boundary"; -import { getDashboardData, refreshDashboardData } from "@/lib/dashboard/service"; +import { getDashboardData } from "@/lib/dashboard/service"; import { DashboardClient } from "@/lib/dashboard/dashboard-client"; -export const dynamic = 'force-dynamic'; // ① 동적 페이지 선언 - -// 대시보드 데이터 로딩 컴포넌트 -async function DashboardContent() { +export default async function IndexPage() { + // domain을 명시적으로 전달 + const domain = "evcp"; + try { - const data = await getDashboardData("evcp"); - + // 서버에서 직접 데이터 fetch + const dashboardData = await getDashboardData(domain); + return ( - + + + ); } catch (error) { - console.error("Dashboard data loading error:", error); - throw error; + console.error("Dashboard data fetch error:", error); + return ( + +
+
+

데이터를 불러오는데 실패했습니다.

+

{error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다."}

+
+
+
+ ); } } -// 대시보드 로딩 스켈레톤 function DashboardSkeleton() { return (
@@ -91,35 +98,3 @@ function DashboardSkeleton() {
); } - -// 에러 표시 컴포넌트 -function DashboardError({ error, reset }: { error: Error; reset: () => void }) { - return ( -
-
-

대시보드를 불러올 수 없습니다

-

- {error.message || "알 수 없는 오류가 발생했습니다."} -

-
- -
- ); -} - -export default async function DashboardPage() { - return ( - - - }> - - - - - ); -} \ No newline at end of file -- cgit v1.2.3